/* 
工具箱
*/
const CHARACTER_SET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

/**
 * 判断有值
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isVal (val) {
  return !(val === undefined || val === null)
}

/**
 * 获取数据精确类型
 * typeof Symbol() -> symbol
   toString.call(Symbol()) -> [object Symbol]
   toString未使用call方法，this始终指向Object: toString(1)->"[object Object]"
   toString.call(document.getElementsByTagName('body'))->"[object HTMLCollection]"
 * @param {Any} val
 * @returns {String}
 */
function inType (val) {
  if (!isVal(val) || val === '') {
    console.warn(`inType方法: 参数是一个空值 ${val}`)
  }
  return Object.prototype.toString.call(val).replace(/\[|\]/g, '').split(/\s/)[1].toLocaleLowerCase()
}

/**
 * 判断布尔
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isBoolean (val) {
  return inType(val) === 'boolean'
}

/**
 * 判断字符串
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isString (val) {
  return inType(val) === 'string'
}

/**
 * 判断数字
 * isNaN 非数字，返回true
 * toString.call(NaN) : number
 * typeof NaN : number
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isNumber (val) {
  return !isNaN(val)
}

/**
 * 判断对象
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isObject (val) {
  return inType(val) === 'object'
}

/**
 * 判断数组
 * 类数组对象也要算在其中
   Arguments [Array(0), callee: ƒ, Symbol(Symbol.iterator): ƒ]
   Array.isArray(arguments) -> false
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isArray (val) {
  return val ? (
    inType(val) === 'array' || (typeof val === 'object' && val.length >= 0)
  ) : false
}

/**
 * 判断日期对象
 * @param {Object} val
 * @returns {Boolean} 是true，否false
 */
function isDate (val) {
  return inType(val) === 'date'
}

/**
 * 判断函数
 * @param {Object} val
 * @returns {Boolean} 是true，否false
 */
function isFunction (val) {
  return inType(val) === 'function'
}

/**
 * 判断window
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isWindow (val) {
  return inType(val) === 'window'
}

/**
 * 判断 document
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isDocument (val) {
  return Object.prototype.toString.call(val) === '[object HTMLDocument]'
}

/**
 * 判断dom节点
 * @param {Any} val
 * @returns {Boolean} 是true，否false
 */
function isDom (val) {
  return typeof HTMLElement !== 'undefined' ? val instanceof HTMLElement
    : (typeof val === 'object' && (val.nodeType === 1 || isVal(val.nodeType)))
}

/**
 * 判断空对象
 * @param {Object} ob
 * @returns {Boolean} 非空返回false
 */
function isEmptyObject (ob) {
  return !!Object.keys(ob).length
}

/**
 * 检查是否时可枚举属性
 * @param {Object} ob
 * @param {String} key
 * @returns {Boolean} 是true，否false
 */
function hasOwn (ob, key) {
  if (Object.prototype.hasOwnProperty) {
    return Object.prototype.hasOwnProperty.call(ob, key)
  } else {
    return isVal(ob[key])
  }
}

/**
 * 随机字符串
 * @param {Number} range
 * @returns {String}
 */
function anystr (range) {
  range = range || 8
  const r = []
  const length = CHARACTER_SET.length
  for (let i = 0; i < range; i++) {
    r.push(CHARACTER_SET.charAt(
      Math.floor(Math.random() * length)
    ))
  }
  return r.join('')
}

/**
 * 模板字符串替换
 * @param {arguments} any
 * @returns {String}
 */
function format () {
  let i = 0
  const args = arguments
  const length = args.length
  return String(args[0]).replace(/%[sdj]/g, function (x) {
    if (i >= length) {
      return x
    }
    i++
    switch (x) {
      case '%s':
        return String(args[i])
      case '%d':
        return Number(args[i])
      case '%j':
        return json2Str(args[i])
      default:
        return x
    }
  })
}

/**
 * 去掉前后空格
 * @param {String} val
 * @returns {String}
 */
function trim (val) {
  return String(val).replace(/(^\s*)|(\s*$)/g, '')
}

/**
 * 去掉前空格
 * @param {String} val
 * @returns {String}
 */
function trimStart (val) {
  return String(val).replace(/^\s*/, '')
}
/**
 * 去掉后空格
 * @param {String} val
 * @returns {String}
 */
function trimEnd (val) {
  return String(val).replace(/\s*$/, '')
}

/**
 * 查找指定字符串
 * String(ob)：将其转换为字符串类
 * @param {any} ob
 * @param {any} val
 * @returns {String}
 */
function findStr (ob, val) {
  return (!isVal(ob) || !isVal(val)) ? false : new RegExp(',' + String(val)  + ',').test(',' + String(ob) + ',')
}

/**
 * 单词首字母大写
 * @param {String} val
 * @returns {String}
 */
function capitalize (val) {
  return String(val).toLowerCase().replace(/( |^)[a-z]/g, function (str) {
    return str.toUpperCase()
  })
}

/**
 * 中文Unicode编码
 * @param {String} val
 * @returns {String}
 */
function unicodeEncode (val) {
  if (!isVal(val)) {
    return ''
  }
  val = String(val)
  const r = []
  for (let i = 0, length = val.length; i < length; i++) {
    // charCodeAt() 方法返回0到65535之间的整数，表示给定索引处的UTF-16代码单元
    // slice(-4): 编码后字符不满足4位的用0填充，并截取最后四位
    r.push(
      ('00' + val.charCodeAt(i).toString(16)).slice(-4)
    )
  }
  return '\\u' + r.join('\\u')
}

/**
 * 中文Unicode解码
 * @param {String} val
 * @returns {String}
 */
function unicodeDecode (val) {
  return !val ? '' : unescape(val.replace(/\\/g, '%'))
}

/**
 * 返回一个新数组
   可以用来将一个类数组（Array-like）对象/集合转换成一个新数组
 * @param {Any} val
 * @returns {Array}
 */
function inArray (val) {
  return !val ? [] : [].slice.call(val)
}

/**
 * JSON对象序列化
 * @param {Object} val
 * @returns {String}
 */
function json2Str (val) {
  return (isObject(val) || isArray(val)) ? JSON.stringify(val) : val
}

/**
 * 转换为JSON对象
 * 正则方法处理有弊端，引号的不确定性（s = s.replace(/'/g, '"')），还是改用eval
 * @param {String} val
 * @returns {Object}
 */
function toJson (val) {
  if (!isString(val)) {
    return val
  }
  try {
    val = JSON.parse(val)
  } catch (e) {
    try {
      /**
        eslint 抛出错误：eval can be harmful.eslint(no-eval)
        解决方式：
        function evl (fn) {
          const Fun = Function // 一个变量指向Function，防止有些前端编译工具报错
          return new Fun('return ' + fn)()
        }
        evl("{\"a\":1}")
     */
      /* eslint-disable-line */ val = eval('(' + val + ')')
    } catch (e) {
      // eval也有处理不了字符串
      console.error(`toJson方法：参数有问题，eval都解析不了`)
    }
  }
  return val
}

/**
 * json对象转参数字符串
 * @param {Object} ob
 * @returns {String}
 */
function json2Param (ob) {
  if (!isObject(ob)) {
    return ob
  }
  return Object.keys(ob).map(k => (`${k}=${json2Str(ob[k])}`)).join('&')
}

/**
 * 参数字符串转json对象
   toJson('{"' + search.replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}') 这种处理不了有json的数据
 * @param {String} ob
 * @returns {Object}
 */
function param2Json (url) {
  if (!isVal(url) || !isString(url)) {
    return {}
  }
  url = decodeURIComponent(url)
  const search = url.substr(url.indexOf('?') + 1, url.length)
  const r = {}
  search.split('&').map(s => s.split('=')).forEach(([a, b] = []) => (r[a] = b))
  return r
}

/**
 * 遍历数组;循环;遍历;循环标签;数组
 * @param {Array|Object} ob
 * @param {Function} fn
 */
function forEach (ob, fn) {
  if (!fn) {
    return
  }
  let o
  if (isArray(ob)) {
    const length = ob.length
    for (let i = 0; i < length; i++) {
      o = ob[i]
      fn.call(o, o, i, ob)
    }
  } else if (isObject(ob)) {
    for (const i in ob) {
      o = ob[i]
      fn.call(o, o, i, ob)
    }
  }
}

/**
 * 合并、扩展、深/浅拷贝多个对象
 * @param {arguments} [Boolean,Object,...]；第一个参数为true时，表示深度合并、深拷贝；否则浅度合并、浅拷贝（后者与Object.assign同理）；
 * @returns {Object}
 * @eg deepClone(true,{},...); deepClone({},{},...)
 */
function deepClone() {
  const length = arguments.length
  let i = 1
  let target = arguments[0]
  let deep = false
  if (isBoolean(target)) {
    deep = target
    target = arguments[i]
    i++
  }
  if (!isObject(target)) {
    target = Object(target)
  }

  let options
  let name
  let copy
  let src
  let item
  let cell
  let j = 0
  for (; i < length; i++) {
    options = arguments[i]
    if (!isObject(options)) {
      continue
    }
    for (name in options) {
      copy = options[name]
      if (!isVal(copy)) {
        continue
      }
      // 防止对象原型被污染，防止永无休止的循环 
      if (name === "__proto__") {
        continue
      }
      src = target[name]
      if (deep && isObject(copy)) {
        if (isObject(src)) {
          target[name] = deepClone(deep, src, copy)
        } else {
          target[name] = toJson(json2Str(copy))
        }
      } if (deep && Array.isArray(copy)) {
        if (Array.isArray(src)) {
          // 这里不处理二维及以上数组
          for (; j < copy.length; j++) {
            item = src[j]
            cell = copy[j]
            if (isObject(item) && isObject(cell)) {
              target[name][j] = deepClone(deep, item, cell)
            } else {
              target[name][j] = cell
            }
          }
        } else {
          target[name] = toJson(json2Str(copy))
        }
      } else {
        target[name] = copy
      }
    }
  }

  return target;
}

/**
 * 格式化对象，粗暴的克隆技术
 * @param {Object} val
 * @return {Object}
 */
function inObject (val) {
  return (isObject(val) || isArray(val)) ? toJson(json2Param(val)) : val
}

/**
 * 获取url中的参数
 * @param {String} name 参数名字
 * @param {String} url url地址
 * @returns {String}
 */
function urlParam (name, url) {
  let str
  if (!url) {
    str = decodeURIComponent(location.search).substr(1)
  } else {
    str = decodeURIComponent(url.split('?'))[1]
  }
  const reg = new RegExp('&\\s*' + name + '\\s*=\\s*([^&]*)&', 'gi')
  const val = reg.exec('&' + str + '&')
  return !val ? '' : trim(val[1])
}

/**
* 会话缓存
* @param {String} name 键
* @param {any} val 值
* @returns {null|String} 值
*/
function sessionCache (name, val) {
  return !val ? sessionStorage.getItem(name) : sessionStorage.setItem(name, json2Str(val))
}

/**
* 本地储存
* @param {String} name 键
* @param {any} val 值
* @returns {null|String} 值
*/
function localCache (name, val) {
  return !val ? localStorage.getItem(name) : localStorage.setItem(name, json2Str(val))
}

module.exports = {
  inType,
  isVal,
  isBoolean,
  isString,
  isNumber,
  isArray,
  isObject,
  isDate,
  isFunction,
  isWindow,
  isDocument,
  isDom,
  isEmptyObject,
  hasOwn,

  anystr,
  format,
  trim,
  trimStart,
  trimEnd,
  findStr,
  capitalize,
  unicodeEncode,
  unicodeDecode,

  inArray,
  json2Str,
  toJson,
  json2Param,
  param2Json,
  forEach,
  deepClone,
  inObject,

  urlParam,
  sessionCache,
  localCache
}
